﻿using System;
using System.Collections.Generic;
using System.Text;
using PDOGenerator.MapTypes;
using System.IO;
using System.CodeDom.Compiler;
using System.Data.Common;
using System.Data;
using System.Data.OleDb;

namespace PDOGenerator.Generators
{
    /// <summary>
    /// NOT CURRENTLY IN ACTIVE USE
    /// From original MoreGen implementation.
    /// </summary>
    class ToCSClass : ClassGenerator
    {
        private const string UserCodeStart = "#region User Generated Code";
        private const string UserCodeEnd = "#endregion // User Generated Code";
        private const string UserCodeMessage = @"//
// The code in this section is automatically copied over when MoreGen generates the class.
//
";
        private Project Proj;
        internal ToCSClass(Project p)
        {
            Proj = p;
        }

        public override string Generate(TableMap Table)
        {
            StringWriter sw = new StringWriter();
            IndentedTextWriter tw = new IndentedTextWriter(sw, "\t");
            tw.Indent = 0;

            if (File.Exists(Proj.OutputDirectory + Table.ClassFileName))
                CopyHeaderArea(tw, Table);
            else
                GenerateHeaderArea(tw, Table);
            BeginNamespace(tw, Table.Project);

            if (!Table.CanCreateEnum())
                Table.CreateEnum = false;

            if (Table.CreateEnum)
            {
                BeginEnum(tw, Table);

                WriteEnumContents(tw, Table);

                EndEnum(tw, Table);
                tw.WriteLine();
            }

            if (Table.CreateClass)
            {
                BeginClass(tw, Table);
                BeginRegion(tw, "Private Fields");
                foreach (ColumnMap c in Table.Columns.Values)
                {
                    WriteField(tw, Table, c);
                }
                EndRegion(tw, "Private Fields");

                BeginRegion(tw, "Public Properties");
                foreach (ColumnMap c in Table.Columns.Values)
                {
                    WriteProperty(tw, Table, c);
                }
                EndRegion(tw, "Public Properties");

                AddUserArea(tw, Table);

                BeginRegion(tw, "Equals and GetHashCode");
                WriteEquals(tw, Table);
                WriteGetHashCode(tw, Table);
                EndRegion(tw, "Equals and GetHashCode");
                EndClass(tw, Table);
            }
            EndNamespace(tw);

            return sw.ToString();
        }

        private void AddUserArea(IndentedTextWriter tw, TableMap Table)
        {
            if (File.Exists(Proj.OutputDirectory + Table.ClassFileName))
            {
                // copy existing area
                StreamReader sr = File.OpenText(Proj.OutputDirectory + Table.ClassFileName);
                string UserCode = "";
                bool InRegion = false;

                try
                {
                    string Line;

                    while ((Line = sr.ReadLine()) != null)
                    {
                        if (Line.StartsWith(UserCodeStart))
                        {
                            InRegion = true;
                        }
                        else if (Line.StartsWith(UserCodeEnd))
                        {
                            UserCode += Line + "\r\n";
                            break;
                        }

                        if (InRegion)
                        {
                            UserCode += Line + "\r\n";
                        }
                    }

                    if (!InRegion)
                    {
                        // not found - insert default region
                        tw.WriteLineNoTabs(UserCodeStart);
                        tw.WriteLineNoTabs(UserCodeMessage);
                        tw.WriteLineNoTabs(UserCodeEnd);
                        tw.WriteLine();
                        return;
                    }

                    tw.WriteLineNoTabs(UserCode);
                }
                finally
                {
                    sr.Close();
                }
            }
            else
            {
                tw.WriteLineNoTabs(UserCodeStart);
                tw.WriteLineNoTabs(UserCodeMessage);
                tw.WriteLineNoTabs(UserCodeEnd);
                tw.WriteLine();
            }
        }

        private void CopyHeaderArea(IndentedTextWriter tw, TableMap Table)
        {
            StreamReader sr = File.OpenText(Proj.OutputDirectory + Table.ClassFileName);
            string Header = "";

            try
            {
                string Line;

                while ((Line = sr.ReadLine()) != null)
                {
                    if (Line.Contains("namespace "))
                        return;

                    Header += Line + "\r\n";
                }

                // if we get here, we got to the end of the file, so revert to old behaviour
                Header = "";
                GenerateHeaderArea(tw, Table);
            }
            finally
            {
                tw.Write(Header);
                sr.Close();
            }
        }

        private void WriteEnumContents(IndentedTextWriter tw, TableMap Table)
        {
            OleDbConnection conn = new OleDbConnection(Proj.ConnectionString);
            conn.Open();

            string pkName = "";
            foreach (ColumnMap col in Table.Columns.Values)
            {
                if (col.IsPK)
                {
                    pkName = col.ColumnName;
                    break;
                }
            }

            string SQLText = "SELECT " + pkName + ", " + Table.EnumField + " FROM " + Table.TableName + " ORDER BY " + pkName;
            OleDbDataAdapter a = new OleDbDataAdapter(SQLText, conn);
            DataTable dt = new DataTable();
            a.Fill(dt);

            for (int i = 0; i < dt.Rows.Count; ++i)
            {
                string Value = (string)dt.Rows[i][1];
                StringBuilder nVal = new StringBuilder();

                for (int j = 0; j < Value.Length; ++j)
                {
                    if (Value[j] == ' ')
                        continue;

                    if (Char.IsLetterOrDigit(Value[j]))
                        nVal.Append(Value[j]);
                    else
                        nVal.Append('_');
                }

                string Line = nVal.ToString() + " = " + dt.Rows[i][0].ToString();

                if (i != dt.Rows.Count - 1)
                    Line += ",";

                tw.WriteLine(Line);
            }

            conn.Close();
        }

        private void EndEnum(IndentedTextWriter tw, TableMap Table)
        {
            tw.Indent--;
            tw.WriteLine("}");
        }

        private void BeginEnum(IndentedTextWriter tw, TableMap Table)
        {
            tw.WriteLine("public enum " + Table.EnumName);
            tw.WriteLine("{");
            tw.Indent++;
        }

        private void WriteGetHashCode(IndentedTextWriter tw, TableMap Table)
        {
            tw.WriteLine("public override int GetHashCode()");
            tw.WriteLine("{");
            tw.Indent++;
            tw.Write("return ");
            bool First = true;
            foreach (ColumnMap c in Table.Columns.Values)
            {
                if (!c.IsPK)
                    continue;

                if (First)
                    First = false;
                else
                    tw.Write(" ^ ");
                tw.Write(c.FieldName + ".GetHashCode()");
            }
            tw.WriteLine(";");
            tw.Indent--;
            tw.WriteLine("}");
        }

        private void WriteEquals(IndentedTextWriter tw, TableMap Table)
        {
            tw.WriteLine("public override bool Equals(object obj)");
            tw.WriteLine("{");
            tw.Indent++;
            tw.WriteLine("if (obj is " + Table.ClassName + ")");
            tw.WriteLine("{");
            tw.Indent++;
            tw.WriteLine(Table.ClassName + " a = (" + Table.ClassName + ")obj;");

            tw.Write("if (");
            bool First = true;
            foreach (ColumnMap c in Table.Columns.Values)
            {
                if (!c.IsPK)
                    continue;

                if (First)
                    First = false;
                else
                    tw.Write(" && ");
                tw.Write("a." + c.FieldName + " == " + c.FieldName);
            }
            tw.WriteLine(")");

            tw.Indent++;
            tw.WriteLine("return true;");
            tw.Indent--;
            tw.Indent--;
            tw.WriteLine("}");

            tw.WriteLine("return false;");
            tw.Indent--;
            tw.WriteLine("}");
        }

        private void WriteProperty(IndentedTextWriter tw, TableMap t, ColumnMap c)
        {
            if(c.IsCollection) 
            {
                if (!c.IsFK || c.ForeignKeyType == ForeignKeyTypes.KeyOnly || c.ForeignKeyType == ForeignKeyTypes.KeyAndReadonlyObject)
                {
                    tw.Write("public virtual ");
                    WriteDataType(tw, t, c);
                    tw.WriteLine(" " + c.PropertyName);
                    tw.WriteLine("{");
                    tw.Indent++;
                    tw.WriteLine("get { return " + c.FieldName + "; }");
                    if (!c.ReadOnly && !c.Table.ReadOnly && !c.Identity && !c.Calculated && c.Sequence == null)
                        tw.WriteLine("set { " + c.FieldName + " = value; }");
                    tw.Indent--;
                    tw.WriteLine("}");
                }
                if (c.IsFK && (c.ForeignKeyType == ForeignKeyTypes.ObjectOnly || c.ForeignKeyType == ForeignKeyTypes.ObjectAndReadonlyKey))
                {
                    tw.Write("public virtual ");
                    tw.Write(Proj.AllTables[c.CollectionTableMoniker].ClassName);
                    tw.WriteLine(" " + c.ObjectPropertyName);
                    tw.WriteLine("{");
                    tw.Indent++;
                    tw.WriteLine("get { return " + c.ObjectFieldName + "; }");
                    if (!c.ReadOnly && !c.Table.ReadOnly && !c.Identity && !c.Calculated && c.Sequence == null)
                        tw.WriteLine("set { " + c.ObjectFieldName + " = value; }");
                    tw.Indent--;
                    tw.WriteLine("}");
                }
                if (c.IsFK && (c.ForeignKeyType == ForeignKeyTypes.KeyAndReadonlyObject))
                {
                    tw.Write("public virtual ");
                    tw.Write(Proj.AllTables[c.CollectionTableMoniker].ClassName);
                    tw.WriteLine(" " + c.ObjectPropertyName);
                    tw.WriteLine("{");
                    tw.Indent++;
                    tw.WriteLine("get { return " + c.ObjectFieldName + "; }");
                    tw.Indent--;
                    tw.WriteLine("}");
                }
                if (c.IsFK && c.ForeignKeyType == ForeignKeyTypes.ObjectAndReadonlyKey)
                {
                    tw.Write("public virtual ");
                    WriteDataType(tw, t, c);
                    tw.WriteLine(" " + c.PropertyName);
                    tw.WriteLine("{");
                    tw.Indent++;
                    tw.WriteLine("get { return " + c.FieldName + "." + c.ForeignTable.PrimaryKey().PropertyName + "; }");
                    tw.Indent--;
                    tw.WriteLine("}");
                }
            }
            else
            {
                // Collection
                tw.Write("public virtual ");
                WriteCollectionType(tw, c, true);
                tw.WriteLine(" " + c.PropertyName);
                tw.WriteLine("{");
                tw.Indent++;

                tw.WriteLine("get");
                tw.WriteLine("{");
                tw.Indent++;
                tw.WriteLine("if (" + c.FieldName + " == null)");
                tw.Indent++;
                tw.Write(c.FieldName + " = new ");
                WriteCollectionType(tw, c, false);
                tw.WriteLine("();");
                tw.Indent--;
                tw.WriteLine();
                tw.WriteLine("return " + c.FieldName + ";");
                tw.Indent--;
                tw.WriteLine("}");

                tw.Indent--;
                tw.WriteLine("}");
            }
        }


        private void WriteField(TextWriter tw, TableMap t, ColumnMap c)
        {
            if(!c.IsCollection) 
            {
                if (!c.IsFK || c.ForeignKeyType == ForeignKeyTypes.KeyAndReadonlyObject || c.ForeignKeyType == ForeignKeyTypes.KeyOnly)
                {
                    tw.Write("private ");
                    WriteDataType(tw, t, c);
                    tw.Write("\t" + c.FieldName + ";");
                    tw.WriteLine();
                }
                else
                {
                    tw.Write("private ");
                    tw.Write(Proj.AllTables[c.CollectionTableMoniker].ClassName);
                    tw.Write("\t" + c.ObjectFieldName + ";");
                    tw.WriteLine();
                }

                if (c.IsFK && c.ForeignKeyType == ForeignKeyTypes.KeyAndReadonlyObject)
                {
                    tw.Write("private ");
                    tw.Write(Proj.AllTables[c.CollectionTableMoniker].ClassName);
                    tw.Write("\t" + c.ObjectFieldName + ";");
                    tw.WriteLine();
                }
            }
            else    // collection
            {
                tw.Write("private ");
                WriteCollectionType(tw, c, true);
                tw.Write("\t" + c.FieldName + ";");
                tw.WriteLine();
            }
        }

        private void WriteCollectionType(TextWriter tw, ColumnMap c, bool Abstract)
        {
            TableMap fTable = Proj.AllTables[c.CollectionTableMoniker];

            switch (c.CollectionType)
            {
                case CollectionTypes.Bag:
                case CollectionTypes.List:
                    if (Abstract)
                        tw.Write("IList<" + fTable.ClassName + ">");
                    else
                        tw.Write("List<" + fTable.ClassName + ">");
                    break;
                case CollectionTypes.Map:
                    if (Abstract)
                        tw.Write("IDictionary<");
                    else
                        tw.Write("Dictionary<");
                    WriteDataType(tw, fTable, fTable.Columns[c.IndexColumn]);
                    tw.Write(", " + fTable.ClassName + ">");
                    break;
            }
        }

        private void WriteDataType(TextWriter tw, TableMap t, ColumnMap c)
        {
            if (c.IsPK && t.CreateEnum)
            {
                tw.Write(t.EnumName);
            }
            else
            {
                tw.Write(c.DataType);
                if (c.Nullable)
                {
                    tw.Write("?");
                }
            }
        }

        private void EndRegion(IndentedTextWriter tw, string p)
        {
            tw.WriteLineNoTabs("#endregion //" + p);
            tw.WriteLine();
        }

        private void BeginRegion(IndentedTextWriter tw, string p)
        {
            tw.WriteLineNoTabs("#region " + p);
        }

        private void EndClass(IndentedTextWriter tw, TableMap Table)
        {
            tw.Indent--;
            tw.WriteLine("}");
        }

        private void BeginClass(IndentedTextWriter tw, TableMap Table)
        {
            tw.WriteLine("/// <summary>");
            tw.WriteLine("/// " + Table.ClassName + " object for NHibernate mapped table " + Table.TableName + ".");
            tw.WriteLine("/// </summary>");
            tw.WriteLine("public class " + Table.ClassName);
            tw.WriteLine("{");
            tw.Indent++;
        }

        private void EndNamespace(IndentedTextWriter tw)
        {
            tw.Indent--;
            tw.WriteLine("}");
        }

        private void BeginNamespace(IndentedTextWriter tw, Project proj)
        {
            tw.WriteLine("namespace " + proj.NameSpace);
            tw.WriteLine("{");
            tw.Indent++;
        }

        private void GenerateHeaderArea(TextWriter tw, TableMap Table)
        {
            tw.WriteLine("//");
            tw.WriteLine("//Generated by MoreGen O/R Generator");
            tw.WriteLine("//");
            tw.WriteLine();
            tw.WriteLine("using System;");
            tw.WriteLine("using System.Collections;");
            tw.WriteLine("using System.Collections.Generic;");
            tw.WriteLine();
        }
    }
}
